﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Graphics.PackedVector;



namespace WindowsGame
{
    public class TerrainRenderer
    {
        public uint numVertices = 0;
        public uint numTriangles = 0;

        public VertexBuffer terrainPatchVBuff;
        public IndexBuffer terrainPatchIBuff;
        VertexDeclaration vertexTextureDec;

        TerrainResources terrainResources;

		IGameLogicService gameLogicService;

        GraphicsDevice graphicsDevice;



        public TerrainRenderer(GraphicsDevice graphicsDeviceIn, TerrainResources terrainResourcesIn,
			IGameLogicService gameLogicServiceIn)
        {
            graphicsDevice = graphicsDeviceIn;
            terrainResources = terrainResourcesIn;
			gameLogicService = gameLogicServiceIn;

            vertexTextureDec = new VertexDeclaration(new VertexElement[]
            {
                new VertexElement(0, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
            }
            );
        }



        void BuildTerrainPatchGeometry(int numSideVertices)
        {
            Debug.Assert(vertexTextureDec != null);
            Debug.Assert(graphicsDevice != null);
			Debug.Assert(numSideVertices > 1);
			Debug.Assert(numVertices > 0 && numTriangles > 0);

			uint numSideCell = (uint)numSideVertices - 1;
            uint numIndices = numTriangles * 3;

            Vector2[] vertices = new Vector2[numVertices];

            uint k = 0;
			for(uint i = 0; i < numSideVertices; ++i)
				for(uint j = 0; j < numSideVertices; ++j)
                {
                    vertices[k].X = (float)j / numSideCell;
                    vertices[k].Y = (float)i / numSideCell;

                    ++k;
                }

            terrainPatchVBuff = new VertexBuffer(graphicsDevice, vertexTextureDec, (int)numVertices, BufferUsage.WriteOnly);
            terrainPatchVBuff.SetData<Vector2>(vertices);

            Int16[] indices = new Int16[(int)numIndices];

            k = 0;
            for(uint i = 0; i < numSideCell; ++i)
                for(uint j = 0; j < numSideCell; ++j)
                {
                    if((i % 2 == 0 && j % 2 == 0) || (i % 2 != 0 && j % 2 != 0))
                    {
						indices[k] = (Int16)((i + 1) * numSideVertices + j + 1);
						indices[k + 1] = (Int16)((i + 1) * numSideVertices + j);
						indices[k + 2] = (Int16)(i * numSideVertices + j);

						indices[k + 3] = (Int16)(i * numSideVertices + j);
						indices[k + 4] = (Int16)(i * numSideVertices + j + 1);
						indices[k + 5] = (Int16)((i + 1) * numSideVertices + j + 1);
                    }
                    else
                    {
						indices[k] = (Int16)(i * numSideVertices + j);
						indices[k + 1] = (Int16)(i * numSideVertices + j + 1);
						indices[k + 2] = (Int16)((i + 1) * numSideVertices + j);

						indices[k + 3] = (Int16)((i + 1) * numSideVertices + j);
						indices[k + 4] = (Int16)(i * numSideVertices + j + 1);
						indices[k + 5] = (Int16)((i + 1) * numSideVertices + j + 1);
                    }

                    k += 6;
                }

            terrainPatchIBuff = new IndexBuffer(graphicsDevice, typeof(Int16), (int)numIndices, BufferUsage.WriteOnly);
            terrainPatchIBuff.SetData<Int16>(indices);
        }



        public void LoadContent()
        {
            Debug.Assert(terrainResources != null);
			Debug.Assert(gameLogicService != null);

            terrainResources.terrainPatchEffect.Parameters["worldMat"].SetValue(Matrix.Identity);
            terrainResources.terrainPatchEffect.Parameters["heightMapAtlasTex"].SetValue(terrainResources.heightMapAtlas);
            terrainResources.terrainPatchEffect.Parameters["blendingAtlasTex"].SetValue(terrainResources.paintedMaterialatlas);
            terrainResources.terrainPatchEffect.Parameters["mat1Tex"].SetValue(terrainResources.material1);
            terrainResources.terrainPatchEffect.Parameters["mat2Tex"].SetValue(terrainResources.material2);
            terrainResources.terrainPatchEffect.Parameters["mat3Tex"].SetValue(terrainResources.material3);
            terrainResources.terrainPatchEffect.Parameters["mat4Tex"].SetValue(terrainResources.material4);
            terrainResources.terrainPatchEffect.Parameters["mat5Tex"].SetValue(terrainResources.material5);
            terrainResources.terrainPatchEffect.Parameters["matSlopeTex"].SetValue(terrainResources.materialSlope);

			terrainResources.terrainPatchEffect.Parameters["gridStep"].SetValue(terrainResources.cellSpacing);
			terrainResources.terrainPatchEffect.Parameters["heightScale"].SetValue(terrainResources.heightScale);
			terrainResources.terrainPatchEffect.Parameters["textureScale"].SetValue(terrainResources.materialScale);
			terrainResources.terrainPatchEffect.Parameters["slopeFactor"].SetValue(terrainResources.slopeFactor);
			terrainResources.terrainPatchEffect.Parameters["atlasDim"].SetValue(terrainResources.atlasDim);
			terrainResources.terrainPatchEffect.Parameters["atlasNumCols"].SetValue(terrainResources.atlasNumTilePerCols);
			terrainResources.terrainPatchEffect.Parameters["atlasTileDim"].SetValue(terrainResources.atlasTileDim);
			terrainResources.terrainPatchEffect.Parameters["atlasPadding"].SetValue(terrainResources.atlasPadding);

            terrainResources.terrainPatchEffect.CurrentTechnique =
                terrainResources.terrainPatchEffect.Techniques["TerrainPatchTechnique"];

			numVertices = (uint)(terrainResources.atlasTileDim * terrainResources.atlasTileDim);
			numTriangles = (uint)((terrainResources.atlasTileDim - 1) * (terrainResources.atlasTileDim - 1) * 2);
			BuildTerrainPatchGeometry(terrainResources.atlasTileDim);
        }



        public void Draw(GameTime gameTime)
        {
            Debug.Assert(graphicsDevice != null);
            Debug.Assert(terrainResources != null);
			Debug.Assert(gameLogicService != null);
			Debug.Assert(numVertices > 0 && numTriangles > 0);

            graphicsDevice.Indices = terrainPatchIBuff;
            graphicsDevice.SetVertexBuffer(terrainPatchVBuff);

			terrainResources.terrainPatchEffect.Parameters["worldViewProjMat"].SetValue(
				Matrix.Multiply(gameLogicService.GameState_.camera.viewMatrix.Value.GetNative(), gameLogicService.ProjectionMat));

			terrainResources.terrainPatchEffect.Parameters["lightVectorW"].SetValue(gameLogicService.Lights.LightVector);
			terrainResources.terrainPatchEffect.Parameters["diffuseLightMtrl"].SetValue(gameLogicService.Lights.DiffuseLightMtrl);
			terrainResources.terrainPatchEffect.Parameters["ambientLightMtrl"].SetValue(gameLogicService.Lights.AmbientLightMtrl);

			int lastPatchIndex = terrainResources.atlasNumActiveTile - 1;
			int currentPatchIndex = lastPatchIndex - gameLogicService.Terrain_.currentPatchIndex.Value;
			int startPatchIndex = currentPatchIndex > 0 ? currentPatchIndex - 1 : currentPatchIndex;

			int endPatchIndex = currentPatchIndex < lastPatchIndex ? currentPatchIndex + 1 : currentPatchIndex;

			for(int i = startPatchIndex; i <= endPatchIndex; i++)
            {
                terrainResources.terrainPatchEffect.Parameters["terrainPatchIndex"].SetValue(i);

                foreach (EffectPass pass in terrainResources.terrainPatchEffect.CurrentTechnique.Passes)
                {
                    pass.Apply();

                    graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, (int)numVertices, 0, (int)numTriangles);
                }
            }
        }
    }
}